package com.domino.server.persistence.gamesession;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.logging.Logger;

import javax.persistence.EntityManager;
import javax.persistence.OptimisticLockException;
import javax.persistence.Query;
import javax.persistence.TemporalType;

import com.domino.server.cache.GameSessionData;
import com.domino.server.cache.State;
import com.domino.server.persistence.EMF;
import com.domino.shared.Player;
import com.domino.shared.PlayerId;
import com.domino.shared.session.SessionId;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;

public class GameSessionDaoImpl implements GameSessionDao {

	Logger LOG = Logger.getLogger(getClass().getCanonicalName());

	/* (non-Javadoc)
	 * @see com.domino.server.persistence.gamesession.GameSessionDao#create(com.domino.server.cache.GameSessionData)
	 */
	public void create(GameSessionData gsd) {
		EntityManager em = null;
		try {
			em = EMF.create();
			em.getTransaction().begin();
			gsd.setState(State.PENDING);
			em.persist(gsd);
			em.getTransaction().commit();
			SessionId id = gsd.getSessionId();
		} finally {
			if (em!=null) em.close();
		}
	}

	/*(non-Javadoc)
	 * @see com.domino.server.persistence.gamesession.GameSessionDao#update(com.domino.server.cache.GameSessionData)
	 */
	public void update(GameSessionData gsd) throws OptimisticLockException {
		EntityManager em = null;
		try {
			em = EMF.create();
			em.merge(gsd);
		} finally {
			if (em!=null) em.close();
		}
	}

	/* (non-Javadoc)
	 * @see com.domino.server.persistence.gamesession.GameSessionDao#findByNaturalId(com.domino.shared.session.SessionId)
	 */
	public GameSessionData findByNaturalId(SessionId sessionId) {
		EntityManager em = null;
		try {
			em = EMF.create();
			Key key = KeyFactory.createKey(GameSessionData.class
					.getSimpleName(), Long.parseLong(sessionId.getId()));
			GameSessionData gsd = em.find(GameSessionData.class, key);
			return gsd;
		} finally {
			if (em!=null) em.close();
		}
	}

	/* (non-Javadoc)
	 * @see com.domino.server.persistence.gamesession.GameSessionDao#findByStateAndSize(int, int, int)
	 */
	public List<GameSessionData> findByState(int state,int maxResultSet) {
		EntityManager em = null;
		try {
			em = EMF.create();
			Query q = em.createQuery(
					"SELECT gsd FROM GameSessionData gsd WHERE state = "
							+ state)
					.setParameter("state", state).setMaxResults(maxResultSet);
			List<GameSessionData> results = q.getResultList();
			return new ArrayList<GameSessionData>(results);
		} finally {
			if (em!=null) em.close();
		}
	}

	/* (non-Javadoc)
	 * @see com.domino.server.persistence.gamesession.GameSessionDao#findByDateTimeIntervall(java.util.Date, java.util.Date, int)
	 */
	public List<GameSessionData> findByDateTimeIntervall(Date from, Date to,
			int maxSize) {
		assertNotNull(from, "from");
		assertNotNull(to, "to");

		EntityManager em = null;
		try {
			em = EMF.create();
			Query q = em
					.createQuery(
							"SELECT gsd FROM GameSessionData gsd WHERE gsd.lastUpdated > :from AND gsd.lastUpdated < :to ORDER BY gsd.lastUpdated DESC")
					.setParameter("from", from, TemporalType.DATE)
					.setParameter("to", to, TemporalType.DATE).setMaxResults(
							maxSize);
			List<GameSessionData> sessions = q.getResultList();
			return new ArrayList<GameSessionData>(sessions);
		} finally {
			if (em!=null) em.close();
		}
	}
	
	/* (non-Javadoc)
	 * @see com.domino.server.persistence.gamesession.GameSessionDao#findByDateTimeIntervallAndState(java.util.Date, java.util.Date, int, java.lang.Integer)
	 */
	public List<GameSessionData> findByDateTimeIntervallAndState(Date from, Date to, int state, Integer maxSize) {
		assertNotNull(from, "from");
		assertNotNull(to, "to");

		EntityManager em = null;
		try {
			em = EMF.create();
			Query q = em
					.createQuery(
							"SELECT gsd FROM GameSessionData gsd WHERE gsd.state = :state AND gsd.lastUpdated > :from AND gsd.lastUpdated < :to ORDER BY gsd.lastUpdated DESC")
					.setParameter("from", from, TemporalType.DATE)
					.setParameter("to", to, TemporalType.DATE).setParameter("state", state).setMaxResults(
							maxSize);
			List<GameSessionData> sessions = q.getResultList();
			return new ArrayList<GameSessionData>(sessions);
		} finally {
			if (em!=null) em.close();
		}
	}

	/* (non-Javadoc)
	 * @see com.domino.server.persistence.gamesession.GameSessionDao#countByState(int)
	 */
	public int countByState(int state) {
		EntityManager em = null;
		try {
			em = EMF.create();
			Query q = em
					.createQuery(
							"SELECT COUNT(gsd) FROM GameSessionData gsd WHERE state = :state")
					.setParameter("state", state);
			Number count = (Number) q.getSingleResult();
			return count.intValue();
		} finally {
			if (em!=null) em.close();
		}
	}

	private void assertNotNull(Object object, String parameter) {
		if (object == null) {
			throw new IllegalArgumentException("Parameter '" + parameter
					+ "' cannot be null");
		}
	}

	@Override
	public GameSessionData findPendingOrRunningPlayer(PlayerId playerId) {
		EntityManager em = null;
		try {
			em = EMF.create();
			Query q = em.createQuery(
					"SELECT gsd FROM GameSessionData gsd WHERE state = :state AND playerBlackId = :black")
					.setParameter("state", State.RUNNING).setParameter("black", playerId.getId()).setMaxResults(2);
			List<GameSessionData> results = q.getResultList();
			if (results.size()==0) {
				q = em.createQuery(
				"SELECT gsd FROM GameSessionData gsd WHERE state = :state AND playerWhiteId = :white")
				.setParameter("state", State.RUNNING).setParameter("white", playerId.getId()).setMaxResults(2);
				results = q.getResultList();
			}
			if (results.size()==0) {
				q = em.createQuery(
				"SELECT gsd FROM GameSessionData gsd WHERE state = :state AND playerWhiteId = :white")
				.setParameter("state", State.PENDING).setParameter("white", playerId.getId()).setMaxResults(2);
				results = q.getResultList();
			}
			if (results.size()==0) {
				q = em.createQuery(
				"SELECT gsd FROM GameSessionData gsd WHERE state = :state AND playerBlackId = :black")
				.setParameter("state", State.PENDING).setParameter("black", playerId.getId()).setMaxResults(2);
				results = q.getResultList();
			}
			if (results.size()==0) {
				return null;
			}
			if (results.size()!=1) {
				handleError(em, results);
				throw new IllegalStateException("Can only be one, was:" + results);
			}
			return results.iterator().next();
		} finally {
			if (em!=null) em.close();
		}
	}


	private void handleError(EntityManager em, List<GameSessionData> sessions) {
		for (GameSessionData gsd : sessions) {
			LOG.severe("Put session in error state (duplicate participant): "
					+ gsd);
			gsd.setState(State.ERROR_STATE);
			em.merge(gsd);
		}
	}

}
